Skip to content

Fail closed on unlabeled X posts and scrub upload provenance - #44

Open
0thernet wants to merge 5 commits into
mainfrom
cursor/x-unlabeled-scrub-readback-c6e5
Open

Fail closed on unlabeled X posts and scrub upload provenance#44
0thernet wants to merge 5 commits into
mainfrom
cursor/x-unlabeled-scrub-readback-c6e5

Conversation

@0thernet

@0thernet 0thernet commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Closed PRs #24 and #26 documented the unlabeled-copy rule but did not land. #39 later shipped skill guidance, empty semantic_annotation_ids, and rejection of invented CreateTweet AI fields. That is still not enough: X can auto-apply Made with AI from C2PA or other provenance in uploaded bytes even when Content disclosure is OFF. The composer post https://x.com/hraness/status/2091626299513041128 shows the live failure — disclosure OFF before Post, then a locked/greyed toggle after X auto-labeled.

This PR adds a durable publish-path fix. It does not reopen #24 or #26.

Media scrub before upload

JPEG and PNG attachments are re-encoded to pixels-only bytes before INIT/APPEND (x-web) or the official one-shot upload. Ancillary provenance is dropped: caBX/C2PA, XMP/iTXt, Exif, IPTC, and Adobe APP/COM markers. If the bytes that would be uploaded still match /c2pa|caBX|trainedAlgorithmic|digitalSourceType/i, the run fails before dispatch. x-web posts.publish remains PNG-or-MP4; official x still accepts JPEG/PNG for one-shot images. GIFs are scanned, not re-encoded.

Fail-closed Made with AI readback

After CreateTweet (and the independent TweetResultByRestId read used by the live web UI), a Made with AI / AI-generated / sparkle annotation is a terminal unlabeled-copy failure. The exact error is:

X applied Made with AI label; publish failed for unlabeled-copy policy

The post may already exist. Wrench does not delete it. The run journal forbids failed after dispatch starts, so x-web reports indeterminate with that error and keeps the ledger.

x-web rejects made_with_ai, content_disclosure, ai_generated_disclosure, and semantic_annotation_ids before bootstrap. CreateTweet still sends empty semantic_annotation_ids and no invented disclosure field.

Official x still omits made_with_ai unless the caller explicitly sets true for authorized AI media, and it rejects a labeled create response when that flag is not true.

Skill

skills/wrench/references/x-ai-disclosure.md now documents: prefer Wrench; scrub media; never set made_with_ai for user-supplied copy; composer only as a last resort with disclosure OFF; live permalink check; locked toggle = auto-label = failed publish; do not delete unless the user asks. Package smoke and the skill test fail if a packaged skill drops the unlabeled rule.

R3 preview/confirm is unchanged except for this new terminal failure mode.

Linux Required flake

The first Linux check run failed only in persistent read client > discards live output when an admission holder replaces auth a-to-b. A concurrent admission rewrite can throw could not safely open optional read projection admission (state file changed while it was read) before the auth-realm check. After live I/O that is now remapped to the same discard error so the live result cannot be published. macOS check was already green.

Open in Web Open in Cursor 

Re-encode JPEG and PNG attachments to pixels-only bytes before X upload,
reject residual C2PA/caBX markers, and treat a live Made with AI sparkle
on CreateTweet or TweetResultByRestId as an unlabeled-copy failure. Do
not delete the post. x-web still sends empty semantic_annotation_ids and
rejects invented disclosure inputs.

Co-authored-by: ben <0thernet@users.noreply.github.com>
@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
wrench Ready Ready Preview Aug 24, 2026 12:24am

Request Review

The GIF provenance scan now rematerializes the file before INIT. A
same-second rewrite can match inode identity and still fail the digest
check. Both outcomes reject the upload before any media request.

Co-authored-by: ben <0thernet@users.noreply.github.com>
The posts.publish implementation string now includes pixels-only scrub
and fail-closed Made with AI readback, so the pinned contract hash
moves with that reviewed description.

Co-authored-by: ben <0thernet@users.noreply.github.com>
Current x-web and official x closures now include pixels-only image
re-encoding and Made with AI label detection, so the durable contract
inventory hashes move with those sources.

Co-authored-by: ben <0thernet@users.noreply.github.com>
After live I/O, an optional admission file that changes while it is
read is the same fail-closed outcome as an auth-realm mismatch. Remap
that helper error so Linux cannot publish the discarded result under a
lower-level open failure.

Co-authored-by: ben <0thernet@users.noreply.github.com>
@0thernet
0thernet marked this pull request as ready for review August 24, 2026 00:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants